package org.nlp2rdf.navigator.component.search;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.aksw.commons.util.strings.BifContains;
import org.apache.log4j.Logger;
import org.dllearner.utilities.Files;
import org.nlp2rdf.navigator.client.common.ExampleDTO;
import org.nlp2rdf.navigator.client.common.SearchDescriptorDTO;
import org.nlp2rdf.navigator.client.exception.NKEQueryException;
import org.nlp2rdf.navigator.component.Component;
import org.nlp2rdf.navigator.component.exception.ComponentInitException;
import org.nlp2rdf.navigator.refactored.SparqlService;

import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.sparql.resultset.ResultSetRewindable;

/**
 * SELECT DISTINCT ?object ?label ?comment {
 * ?object a ?class .
 * ?object rdfs:label ?label .
 * ?object rdfs:comment ?comment .
 * FILTER (bif:contains ( ?label , '***BIF:CONTAINS***')) .
 * FILTER (?class LIKE <http://dbpedia.org/ontology/%> ) .
 * } LIMIT	***LIM***
 * ***SEARCH*** gives the pure search value
 *
 * @author Sebastian Hellmann <hellmann@informatik.uni-leipzig.de>
 */
// TODO find better names and better configuration
public class SearchSparql extends Component implements ISearch {
    private static final Logger logger = Logger.getLogger(SearchSparql.class);

    public static final String SEARCH_TEMPLATE = "***SEARCH***";
    public static final String LIMIT_TEMPLATE = "***LIM***";
    public static final String BIF_CONTAINS_TEMPLATE = "***BIF:CONTAINS***";

    public static final String LABEL_VARIABLE = "label";
    public static final String COMMENT_VARIABLE = "comment";
    public static final String OBJECT_VARIABLE = "object";

    private SparqlService sparqlService;

    private String searchId;
    private String label;
    private String comment = "";
    private String searchType = null;

    private String searchQueryFile = null;
    private String sparqlQueryTemplate = null;

    private boolean useSearchLabel = true;
    private boolean useSearchComment = true;

    /*
      * (non-Javadoc)
      *
      * @see org.nlp2rdf.navigator.component.Component#_init()
      */

    @Override
    protected void _init() throws ComponentInitException {
        searchQueryFile = getComponentConfig().getPathModifier() + searchQueryFile;
        try {
            sparqlQueryTemplate = Files.readFile(new File(searchQueryFile)).trim();

        } catch (FileNotFoundException e) {
            String message = "file with sparqlquery not found " + searchQueryFile;
            logger.error(message, e);
            throw new ComponentInitException(e);
        } catch (IOException e) {
            String message = "problem reading from " + searchQueryFile;
            logger.error(message, e);
            throw new ComponentInitException(e);
        }
        boolean checked = true;
        checked = checked && (sparqlQueryTemplate.contains(OBJECT_VARIABLE));
        logger.debug(checked + " passed " + OBJECT_VARIABLE);
        checked = checked && (sparqlQueryTemplate.contains(SEARCH_TEMPLATE) || sparqlQueryTemplate.contains(BIF_CONTAINS_TEMPLATE) );
        logger.debug(checked + " passed " + SEARCH_TEMPLATE);
        checked = checked && (sparqlQueryTemplate.contains(LIMIT_TEMPLATE));
        logger.debug(checked + " passed " + LIMIT_TEMPLATE);
        if (useSearchLabel) {
            checked = checked && (useSearchLabel == sparqlQueryTemplate.contains("?" + LABEL_VARIABLE));
        }
        logger.debug(checked + " passed " + LABEL_VARIABLE);
        if (useSearchComment) {
            checked = checked && (useSearchComment == sparqlQueryTemplate.contains("?" + COMMENT_VARIABLE));
        }
        logger.debug(checked + " passed " + COMMENT_VARIABLE);

        if (!checked) {
            String msg = "sparql query template must contain the following:\n" + "?" + OBJECT_VARIABLE + "\n" + "" + SEARCH_TEMPLATE + "\n" + "" + LIMIT_TEMPLATE + "\n" + ((useSearchLabel) ? "(OPTIONAL a variable for label ) ?" + LABEL_VARIABLE + "\n" : "") + ((useSearchComment) ? "(OPTIONAL a variable for comment ) ?" + COMMENT_VARIABLE + "\n" : "") + sparqlQueryTemplate;
            logger.error(msg);
            throw new ComponentInitException(msg);
        }

    }

    /*
      * (non-Javadoc)
      *
      * @see
      * org.nlp2rdf.navigator.component.search.ISearch#search(java.lang.String,
      * int)
      */

    public List<Result> search(String search, int nrOfResults) throws NKEQueryException {
        check();
        String sparqlQuery = replaceInQuery(search,nrOfResults);
        logger.trace(sparqlQuery);
        return execute(sparqlQuery);
    }

    /*
      * (non-Javadoc)
      *
      * @see
      * org.nlp2rdf.navigator.component.search.ISearch#search(java.lang.String,
      * int)
      */

    @SuppressWarnings("deprecation")
    @Deprecated
    public List<ExampleDTO> searchOld(String search, int nrOfResults) throws NKEQueryException {
        check();

        List<ExampleDTO> exs = new ArrayList<ExampleDTO>();
        for (Result r : search(search, nrOfResults)) {
            ExampleDTO ex = new ExampleDTO();
            ex.setUri(r.uri);
            ex.setLabel(r.label);
            ex.setComment(r.comment);
            exs.add(ex);
        }
        return exs;

    }

    public String replaceInQuery(String search, int nrOfResults){
         String sparqlQuery = sparqlQueryTemplate;
        sparqlQuery = sparqlQuery.replace(SEARCH_TEMPLATE, search);
        sparqlQuery = sparqlQuery.replace(LIMIT_TEMPLATE, nrOfResults + "");

        String bifContains = new BifContains(search).makeWithAnd();
        sparqlQuery = sparqlQuery.replace(BIF_CONTAINS_TEMPLATE, bifContains);
        return sparqlQuery;

    }

    /*
      * (non-Javadoc)
      *
      * @see
      * org.nlp2rdf.navigator.component.search.ISearch#getNegatives(java.lang
      * .String, int)
      */

    public List<ExampleDTO> getNegatives(String search, int nrOfResults) throws NKEQueryException {
        return new ArrayList<ExampleDTO>();
    }


    private List<Result> execute(String sparqlQuery) throws NKEQueryException {

        List<Result> results = new ArrayList<Result>();
        ResultSetRewindable rsw = null;
        try {

            // execute the query
            rsw = sparqlService.queryAsResultSet(sparqlQuery);

            String uri = null;
            String label = null;
            String comment = null;
            while (rsw.hasNext()) {
                QuerySolution qs = rsw.nextSolution();

                // set the uri
                uri = hackgetURI(qs, OBJECT_VARIABLE);

                if (useSearchLabel) {
                    label = hackQStoString(qs, LABEL_VARIABLE);
                }
                if (useSearchComment) {
                    comment = hackQStoString(qs, COMMENT_VARIABLE);
                }

                if (uri == null) {
                    logger.error("uri must not be null, skipping one result of " + sparqlQuery);
                    continue;
                }

                Result r = new Result(uri, label, comment);
                logger.debug("Found in search: " + r);
                results.add(r);
            }
        } catch (NKEQueryException e) {
            logger.error(e);
            // pass checked exception
            throw e;
        } catch (Exception e) {
            logger.error(sparqlQuery, e);
            throw new NKEQueryException(e.getMessage());
        }

        if (results.isEmpty()) {
            logger.warn("sparqlquery did not return anything, better check: \n" + sparqlQuery);
        }
        return results;
    }

    /**
     * FOR VIRTUOSO
     *
     * @param qs
     * @param var
     * @return
     */
    @Deprecated
    private String hackQStoString(QuerySolution qs, String var) {

        try {
            return qs.getLiteral(var).getLexicalForm();
        } catch (Exception e) {
            logger.warn("still not working correct: hackQStoString " + e.getMessage());
            return qs.get(var).toString();
        }
    }

    /**
     * VIRTUOSO sometimes outputs uris as literals !?
     *
     * @param qs
     * @param var
     * @return
     */
    @Deprecated
    private String hackgetURI(QuerySolution qs, String var) {
        try {

            return qs.getResource(var).getURI();
        } catch (Exception e) {
            try {
                // logger.warn("still not working correct hackgetURI:  "+e.getMessage());
                return qs.getLiteral(var).getLexicalForm();
            } catch (Exception e2) {
                logger.warn("still not working correct 2: hackQStoString " + e2.getMessage());
                return qs.get(var).toString();
            }
        }

    }

    public SearchDescriptorDTO getSearchDescriptor() {
        SearchDescriptorDTO d = new SearchDescriptorDTO();
        d.setSearchId(searchId);
        d.setLabel(label);
        d.setSearchType(searchType);
        d.setComment(comment);
        return d;
    }

    public void setSparqlService(SparqlService sparqlService) {
        this.sparqlService = sparqlService;
    }

    public void setUseSearchLabel(boolean useSearchLabel) {
        this.useSearchLabel = useSearchLabel;
    }

    public void setUseSearchComment(boolean useSearchComment) {
        this.useSearchComment = useSearchComment;
    }

    public void setSearchType(String searchType) {
        this.searchType = searchType;
    }

    public void setSearchId(String searchId) {
        this.searchId = searchId;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public void setSearchQueryFile(String searchQueryFile) {
        this.searchQueryFile = searchQueryFile;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }
}
